#!/usr/bin/env python
# -*- coding: utf-8 -*-
####################################################################
# File: tv_gtk3.py
# Author: Jean-Marc VARENNE-PAQUET
# Description: This script is a graphical user interface (GUI), 
# that allows users to watch TV, by using the multiposte service 
# from the ADSL and fiber boxes.
# Version: 1.1
# License: GNU GPL v3
####################################################################



# Import
import os, sys, string, datetime, re, urllib2
from gi.repository import Gtk, Gdk



# Initialization
g_channel_name_list = []
g_channel_urls_hash_table = {}
g_channel_programs_hash_table = {}
g_favorite_channel_name_list = []
g_vlc = 'vlc'
g_configuration_directory = os.environ['HOME'] + "/.config/x_multipost_tv"
g_playlist_url_file_path = g_configuration_directory + "/playlist_url.txt"
g_favorites_file_path = g_configuration_directory + "/favorit_channels.txt"
g_tasks_file = g_configuration_directory + "/crontab_tasks.txt"
g_playlist_url = "http://mafreebox.freebox.fr/freeboxtv/playlist.m3u"








##########################################################
# Classes
##########################################################

# The main window
class Ui_MainWindow(Gtk.Window):

	# Constructor
	def __init__(self):
		Gtk.Window.__init__(self)
		self.connect("destroy", self.on_destroy)
		self.set_title("X-Multiposte-TV")
		self.resize(450,750)

		# Create a vertical container to draw widgets in it.
		vbox = Gtk.VBox()
		self.add(vbox)




		#########################################################################################
		# Menu bar
		#########################################################################################

		# Create menu bar
		menu_bar = Gtk.MenuBar()
		vbox.pack_start(menu_bar, False, False, 0)

		## Allow accelerators.
		agr = Gtk.AccelGroup()
		self.add_accel_group(agr)



		# menu File
		menu_item_file = Gtk.MenuItem("Fichier") 
		menu_bar.add(menu_item_file)

		menu_file = Gtk.Menu()
		menu_item_file.set_submenu(menu_file)

		menu_item_open_file = Gtk.ImageMenuItem()
		img = Gtk.Image()
		img.set_from_icon_name("document-open", Gtk.IconSize.MENU)
		menu_item_open_file.set_image(img)
		key, mod = Gtk.accelerator_parse("<Control>O")
		menu_item_open_file.add_accelerator("activate", agr, key, mod, Gtk.AccelFlags.VISIBLE)
		menu_item_open_file.set_label("Ouvrir un enregistrement video...")
		menu_item_open_file.connect("activate", self.openLocalVideo)
		menu_file.add(menu_item_open_file)

		separator = Gtk.SeparatorMenuItem()
		menu_file.add(separator)

		menu_item_exit = Gtk.ImageMenuItem()
		img = Gtk.Image()
		img.set_from_icon_name("application-exit", Gtk.IconSize.MENU)
		menu_item_exit.set_image(img)
		key, mod = Gtk.accelerator_parse("<Control>Q")
		menu_item_exit.add_accelerator("activate", agr, key, mod, Gtk.AccelFlags.VISIBLE)
		menu_item_exit.set_label("Quitter")
		menu_item_exit.connect("activate", self.on_destroy)
		menu_file.add(menu_item_exit)



		# menu TV guide
		menu_item_tv_guide = Gtk.MenuItem("GuideTV") 
		menu_bar.add(menu_item_tv_guide)

		menu_tv_guide = Gtk.Menu()
		menu_item_tv_guide.set_submenu(menu_tv_guide)

		menu_item_open_tv_guide = Gtk.ImageMenuItem()
		img = Gtk.Image()
		img.set_from_icon_name("go-jump", Gtk.IconSize.MENU)
		menu_item_open_tv_guide.set_image(img)
		menu_item_open_tv_guide.set_label("Ouvrir le guide TV")
		menu_item_open_tv_guide.connect("activate", lambda menu_item : Gtk.show_uri(None, "http://streamplayer.free.fr/tv/guidetv.php", Gdk.CURRENT_TIME))
		menu_tv_guide.add(menu_item_open_tv_guide)



		# menu Configuration
		menu_item_configuration = Gtk.MenuItem("Configuration") 
		menu_bar.add(menu_item_configuration)

		menu_configuration = Gtk.Menu()
		menu_item_configuration.set_submenu(menu_configuration)

		menu_item_set_playlist = Gtk.ImageMenuItem()
		img = Gtk.Image()
		img.set_from_icon_name("preferences-system", Gtk.IconSize.MENU)
		menu_item_set_playlist.set_image(img)
		menu_item_set_playlist.set_label("Definir la playlist a utiliser")
		menu_item_set_playlist.connect("activate", self.displayConfigurationWindow)
		menu_configuration.add(menu_item_set_playlist)



		# menu Help
		menu_item_help = Gtk.MenuItem("Aide") 
		menu_bar.add(menu_item_help)

		menu_help = Gtk.Menu()
		menu_item_help.set_submenu(menu_help)

		menu_item_about_app = Gtk.ImageMenuItem()
		img = Gtk.Image()
		img.set_from_icon_name("help-about", Gtk.IconSize.MENU)
		menu_item_about_app.set_image(img)
		menu_item_about_app.set_label("A propos de cette application")
		menu_item_about_app.connect("activate", self.displayAboutAppWindow)
		menu_help.add(menu_item_about_app)





		#########################################################################################
		# Notebook
		#########################################################################################

		# Create Notebook
		notebook = Gtk.Notebook()
		vbox.add(notebook)



		# Page 1
		entry_search = Gtk.Entry()
		entry_search.set_icon_from_icon_name(0, "edit-find")
		entry_search.connect("changed", self.updateModel)


		self.list_store_page1 = Gtk.ListStore(str)
		scrolled_window_page1 = Gtk.ScrolledWindow()
		scrolled_window_page1.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
		treeview_page1 = Gtk.TreeView(self.list_store_page1)
		treeview_page1.set_headers_visible(False)
		treeview_page1.connect("button-press-event", self.on_treeview_page1_button_press_event)
		scrolled_window_page1.add(treeview_page1)

		renderer = Gtk.CellRendererText()
		renderer.set_padding(0,0)
		renderer.set_fixed_size(-1,0)
		column = Gtk.TreeViewColumn("", renderer, text=0)
		treeview_page1.append_column(column)

		vbox_page1 = Gtk.VBox()
		vbox_page1.pack_start(entry_search, False, False, 0)
		vbox_page1.pack_start(scrolled_window_page1, True, True, 0)

		label_all = Gtk.Label("Toutes")
		notebook.insert_page(vbox_page1, label_all, 0)




		# Page 2
		self.list_store_page2 = Gtk.ListStore(str)
		scrolled_window_page2 = Gtk.ScrolledWindow()
		scrolled_window_page2.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
		treeview_page2 = Gtk.TreeView(self.list_store_page2)
		treeview_page2.set_headers_visible(False)
		treeview_page2.connect("button-press-event", self.on_treeview_page2_button_press_event)
		scrolled_window_page2.add(treeview_page2)

		renderer = Gtk.CellRendererText()
		renderer.set_padding(0,0)
		renderer.set_fixed_size(-1,0)
		column = Gtk.TreeViewColumn("", renderer, text=0)
		treeview_page2.append_column(column)

		vbox_page2 = Gtk.VBox()
		vbox_page2.pack_start(scrolled_window_page2, True, True, 0)

		label_favorites = Gtk.Label("Favorites")
		notebook.insert_page(vbox_page2, label_favorites, 1)




		# Display all.
		self.show_all()
		self.set_focus(entry_search)




		#########################################################################################
		# Create a directory for the configuration files
		#########################################################################################

		if not os.path.exists(g_configuration_directory):
			try:
				os.makedirs(g_configuration_directory)
			except OSError as msg:
				print(msg)




		#########################################################################################
		# Load data
		#########################################################################################

		self.readConfigurationFile()
		self.loadPlaylist()
		self.reloadFavoritesFile()


	# On exit, close the application.
	def on_destroy(self, widget):
		Gtk.main_quit()


	# Mouse buttons events on the treeview page 1.
	def on_treeview_page1_button_press_event(self, treeview, event):
		x = int(event.x)
		y = int(event.y)
		time = event.time
		pthinfo = treeview.get_path_at_pos(x, y)
		if pthinfo is not None:
			path, col, cellx, celly = pthinfo
			treeview.grab_focus()
			treeview.set_cursor( path, col, 0)

			# Get clicked channel name
			model = treeview.get_model()
			iter = model.get_iter(path)
			channel_name = model.get_value(iter, 0).decode('utf-8')

			# On double click
			if event.button == 1 and event.type == Gdk.EventType._2BUTTON_PRESS:
				self.watchTV(channel_name)

			# On right click
			if event.button == 3:

				# Contextual menu
				menu = Gtk.Menu()

				menu_item_channel_name = Gtk.MenuItem(channel_name)
				menu_item_channel_name.show()
				menu.append(menu_item_channel_name)

				separator = Gtk.SeparatorMenuItem()
				separator.show()
				menu.append(separator)

				menu_item_watch_tv = Gtk.MenuItem("Regarder")
				menu_item_watch_tv.connect("activate", lambda arg : self.watchTV(channel_name))
				menu_item_watch_tv.show()
				menu.append(menu_item_watch_tv)

				menu_item_watch_and_record = Gtk.MenuItem("Enregistrer et regarder")
				menu_item_watch_and_record.connect("activate", lambda arg : self.watchAndRecordTV(channel_name))
				menu_item_watch_and_record.show()
				menu.append(menu_item_watch_and_record)

				menu_item_plan_record = Gtk.MenuItem("Planifier un enregistrement")
				menu_item_plan_record.connect("activate", lambda arg : self.planRecord(channel_name))
				menu_item_plan_record.show()
				menu.append(menu_item_plan_record)

				menu_item_open_tv_guide = Gtk.MenuItem("Ouvrir le guide TV sur cette chaine")
				menu_item_open_tv_guide.connect("activate", lambda arg : self.openWebTVGuide(channel_name))
				menu_item_open_tv_guide.show()
				menu.append(menu_item_open_tv_guide)

				menu_item_add_to_favorites = Gtk.MenuItem("Ajouter aux favoris")
				menu_item_add_to_favorites.connect("activate", lambda arg : self.addToFavorites(channel_name))
				menu_item_add_to_favorites.show()
				menu.append(menu_item_add_to_favorites)

				menu.popup(None, None, lambda menu, data: (event.get_root_coords()[0], event.get_root_coords()[1], True), None, event.button, event.time)
				return False

			return True

	# Mouse buttons events on the treeview page 2.
	def on_treeview_page2_button_press_event(self, treeview, event):
		x = int(event.x)
		y = int(event.y)
		time = event.time
		pthinfo = treeview.get_path_at_pos(x, y)
		if pthinfo is not None:
			path, col, cellx, celly = pthinfo
			treeview.grab_focus()
			treeview.set_cursor( path, col, 0)

			# Get clicked channel name
			model = treeview.get_model()
			iter = model.get_iter(path)
			channel_name = model.get_value(iter, 0).decode('utf-8')

			# On double click
			if event.button == 1 and event.type == Gdk.EventType._2BUTTON_PRESS:
				self.watchTV(channel_name)

			# On right click
			if event.button == 3:

				# Contextual menu
				menu = Gtk.Menu()

				menu_item_channel_name = Gtk.MenuItem(channel_name)
				menu_item_channel_name.show()
				menu.append(menu_item_channel_name)

				separator = Gtk.SeparatorMenuItem()
				separator.show()
				menu.append(separator)

				menu_item_watch_tv = Gtk.MenuItem("Regarder")
				menu_item_watch_tv.connect("activate", lambda arg : self.watchTV(channel_name))
				menu_item_watch_tv.show()
				menu.append(menu_item_watch_tv)

				menu_item_watch_and_record = Gtk.MenuItem("Enregistrer et regarder")
				menu_item_watch_and_record.connect("activate", lambda arg : self.watchAndRecordTV(channel_name))
				menu_item_watch_and_record.show()
				menu.append(menu_item_watch_and_record)

				menu_item_plan_record = Gtk.MenuItem("Planifier un enregistrement")
				menu_item_plan_record.connect("activate", lambda arg : self.planRecord(channel_name))
				menu_item_plan_record.show()
				menu.append(menu_item_plan_record)

				menu_item_open_tv_guide = Gtk.MenuItem("Ouvrir le guide TV sur cette chaine")
				menu_item_open_tv_guide.connect("activate", lambda arg : self.openWebTVGuide(channel_name))
				menu_item_open_tv_guide.show()
				menu.append(menu_item_open_tv_guide)

				menu_item_add_to_favorites = Gtk.MenuItem("Retirer des favoris")
				menu_item_add_to_favorites.connect("activate", lambda arg : self.removeFromFavorites(channel_name))
				menu_item_add_to_favorites.show()
				menu.append(menu_item_add_to_favorites)

				menu.popup(None, None, lambda menu, data: (event.get_root_coords()[0], event.get_root_coords()[1], True), None, event.button, event.time)
				return False

			return True

	# Update the channels list displayed. It takes into account the filter from the search field. 
	def updateModel(self, entry_search=None):
		string = ""
		if entry_search != None:
			string = entry_search.get_text().decode('utf-8')
		string_list = []
		for channel_name in g_channel_name_list:
			minus_channel_name = channel_name.lower()
			minus_string = string.lower()
			if minus_channel_name.find(minus_string) >= 0:
				string_list.append(channel_name)
		self.list_store_page1.clear()
		for channel_name in string_list:
			self.list_store_page1.append([channel_name])

	# Run VLC to display a channel.
	def watchTV(self, channel_name):
		channel_url = g_channel_urls_hash_table[channel_name]
		command = g_vlc + " --video-filter=deinterlace --deinterlace-mode=yadif2x \"" + channel_url + "\" &"
		print(command)
		os.system(command)

	# Run VLC to watch and record a channel at the same time.
	def watchAndRecordTV(self, channel_name):
		channel_url = g_channel_urls_hash_table[channel_name]
		output_file_path = os.environ['HOME'] + os.sep + channel_name.encode('utf-8').replace(" ","") + "__" + str(datetime.datetime.today()).replace(":","-").replace(" ","__") + ".ts"
		command = g_vlc + " \"" + channel_url + "\" --sout=\"#duplicate{dst=std{access=file,mux=ts,dst=" + output_file_path + "},dst=display}\" &"
		print(command)
		os.system(command)

	# Display a window which allows to plan a record from a date to an other.
	def planRecord(self, channel_name):
		channel_url = g_channel_urls_hash_table[channel_name]
		plan_record_dialog = Ui_RecordPlanningWindow(channel_name, channel_url)
		plan_record_dialog.run()
		plan_record_dialog.destroy()

	# Open a web site on the corresponding channel TV guide.
	def openWebTVGuide(self, channel_name):
		channel_number = ""
		myPattern = re.compile('^(\d+)')
		m = re.search(myPattern, channel_name)
		try:
			channel_number = m.groups(1)[0]
		except:
			pass
		Gtk.show_uri(None, "http://streamplayer.free.fr/tv/guidetv.php?chaines=freebox-tv#c" + channel_number, Gdk.CURRENT_TIME)

	# Add a channel in the favorite channels list.
	def addToFavorites(self, channel_name):
		global g_favorite_channel_name_list
		g_favorite_channel_name_list.append(channel_name)
		self.list_store_page2.clear()
		for channel_name in g_favorite_channel_name_list:
			self.list_store_page2.append([channel_name])
		self.updateFavoritesFile()

	# Remove a channel from the favorite channels list.
	def removeFromFavorites(self, channel_name):
		global g_favorite_channel_name_list
		g_favorite_channel_name_list.remove(channel_name)
		self.list_store_page2.clear()
		for channel_name in g_favorite_channel_name_list:
			self.list_store_page2.append([channel_name])
		self.updateFavoritesFile()

	# Update the favorite channels list file.
	def updateFavoritesFile(self):
		try:
			favorite_channels_file = open(g_favorites_file_path,'w')
			for channel_name in g_favorite_channel_name_list:
				favorite_channels_file.write(channel_name.encode('utf-8') + "\n")
			favorite_channels_file.close()
		except IOError as msg:
			print("ERROR: Cannot write in " + g_favorites_file_path)
			print(msg)
			sys.exit(1)

	# Read configuration file
	def readConfigurationFile(self):
		global g_playlist_url
		try:
			configuration_file = open(g_playlist_url_file_path,'r')
			g_playlist_url = configuration_file.readline()
			configuration_file.close()
		except:
			pass

	# Load the remote Freebox playlist file
	def loadPlaylist(self):
		global g_channel_name_list
		global g_channel_urls_hash_table
		g_channel_name_list = []
		g_channel_urls_hash_table = {}

		playlist_file_lines = []
		try:
			playlist_file = urllib2.urlopen(g_playlist_url)
			playlist_file_lines = playlist_file.readlines()
			playlist_file.close()
		except urllib2.URLError as msg:
			print(msg)
			message_dialog = Gtk.MessageDialog(self, 0, Gtk.MessageType.WARNING, Gtk.ButtonsType.CLOSE, "La playlist suivante n'est pas accessible : \n" + g_playlist_url)
			message_dialog.run()
			message_dialog.destroy()

		channel_name = ""
		for line in playlist_file_lines:
			if line.startswith('#EXTINF:'):
				splitted_line = string.split(line,",")
				channel_name = splitted_line[1].rstrip()
				channel_name = channel_name.decode('utf-8')
				g_channel_name_list.append(channel_name)
			if line.startswith('rtsp:'):
				channel_url = line.rstrip()
				g_channel_urls_hash_table[channel_name] = channel_url

		self.updateModel(None)

	# Reload the favorites file
	def reloadFavoritesFile(self):
		global g_favorite_channel_name_list
		if os.path.exists(g_favorites_file_path):
			# Get favorite channels list from the file.
			g_favorite_channel_name_list = []
			file_lines = []
			try:
				favorite_channels_file = open(g_favorites_file_path, 'r')
				file_lines = favorite_channels_file.readlines()
				favorite_channels_file.close()
			except IOError as msg:
				print("ERROR: Cannot read " + g_favorites_file_path)
				print(msg)
				sys.exit(1)

			# Update the favorite channels list and display it.
			self.list_store_page2.clear()
			for line in file_lines:
				line = line.rstrip()
				channel_name = line.decode('utf-8')
				g_favorite_channel_name_list.append(channel_name)
				self.list_store_page2.append([channel_name])

	# Open a window to select a video record from the hard disk.
	def openLocalVideo(self, menu_item):
		local_video_file_path = ""
		chooser_dialog = Gtk.FileChooserDialog("Charger une video", self, Gtk.FileChooserAction.OPEN, buttons=(Gtk.STOCK_CANCEL,Gtk.ResponseType.CANCEL,Gtk.STOCK_OPEN,Gtk.ResponseType.OK))
		chooser_dialog.set_current_folder(os.environ['HOME'])
		my_filter = Gtk.FileFilter()
		my_filter.set_name("Enregistrements")
		my_filter.add_pattern("*.ts")
		chooser_dialog.add_filter(my_filter)
		response = chooser_dialog.run()
		if response == Gtk.ResponseType.OK:
			local_video_file_path = chooser_dialog.get_filename()
		chooser_dialog.destroy()
		if local_video_file_path != "":
			command = g_vlc + " --video-filter=deinterlace --deinterlace-mode=yadif2x \"" + local_video_file_path + "\" &"
			print(command)
			os.system(command)

	# Open a window which displays information about the script.
	def displayAboutAppWindow(self, menu_item):
		about_dialog = Gtk.AboutDialog()
		about_dialog.set_program_name(self.get_title())
		about_dialog.set_version("1.0")
		about_dialog.set_copyright("Logiciel libre, pour transformer son PC Linux en TV.")
		about_dialog.set_license("GNU GPL v3")
		about_dialog.set_authors(["Jean-Marc VARENNE-PAQUET"])
		about_dialog.run()
		about_dialog.destroy()

	# Display a window to set the playlist to use.
	def displayConfigurationWindow(self, menu_item):
		configuration_window = Ui_ConfigurationWindow()
		configuration_window.run()
		configuration_window.destroy()
		self.loadPlaylist()




# The window to set the playlist to use.
class Ui_ConfigurationWindow(Gtk.Dialog):

	# Constructor
	def __init__(self, parent=None):
		Gtk.Dialog.__init__(self, "My Dialog", parent, 0, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OK, Gtk.ResponseType.OK))
		self.connect('response', self.on_response)
		self.set_title("Definir une playlist a utiliser")
		self.resize(450,100)

		# Get area to add widget.
		box = self.get_content_area()

		# Add a single line text entry field.
		self.entry = Gtk.Entry()
		self.entry.set_text(g_playlist_url)
		box.add(self.entry)

		self.show_all()

	# Catch response.
	def on_response(self, dialog, response):
		if response == Gtk.ResponseType.OK:
			# Update the configuration file with the new playlist.
			global g_playlist_url
			g_playlist_url = str(self.entry.get_text())
			try:
				configuration_file = open(g_playlist_url_file_path,'w')
				configuration_file.write(g_playlist_url)
				configuration_file.close()
			except OSError as msg:
				print("WARNING: Cannot write configuration file " + g_playlist_url_file_path)
				print(msg)









# The window to plan video records.
class Ui_RecordPlanningWindow(Gtk.Dialog):

	# Constructor
	def __init__(self, channel_name, channel_url, parent=None):
		Gtk.Dialog.__init__(self, "My Dialog", parent, 0, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OK, Gtk.ResponseType.OK))
		self.connect('response', self.on_response)
		self.set_title("Enregistrement")
		self.channel_name = channel_name
		self.channel_url = channel_url
		date_time_format = "%Y-%m-%d %H:%M"

		# Get area to add widget.
		box = self.get_content_area()

		grid = Gtk.Grid()

		# Display channel name.
		label_channel_name = Gtk.Label(channel_name)
		grid.attach(label_channel_name, 0, 0, 2, 1)

		# Start date
		label_start = Gtk.Label("Date de debut")
		grid.attach(label_start, 0, 1, 1, 1)
		self.entry_start = Gtk.Entry()
		self.entry_start.set_text(str(datetime.datetime.now().strftime(date_time_format)))
		grid.attach(self.entry_start, 1, 1, 1, 1)

		# End date
		label_end = Gtk.Label("Date de fin")
		grid.attach(label_end, 0, 2, 1, 1)
		self.entry_end = Gtk.Entry()
		self.entry_end.set_text(str(datetime.datetime.now().strftime(date_time_format)))
		grid.attach(self.entry_end, 1, 2, 1, 1)

		box.add(grid)

		self.show_all()

	# Catch response.
	def on_response(self, dialog, response):
		if response == Gtk.ResponseType.OK:
			start_date = self.entry_start.get_text()
			print("Start date: " + start_date)
			end_date = self.entry_end.get_text()
			print("End date: " + end_date)

			# Convert dates format to fit the crontab format: mm hh jj MMM JJJ task
			tmp_datetime = datetime.datetime.strptime(start_date, "%Y-%m-%d %H:%M")
			start_date_for_cron = tmp_datetime.strftime("%M %H %d %m") + " *"
			print("Start date for crontab: " + start_date_for_cron)
			tmp_datetime = datetime.datetime.strptime(end_date, "%Y-%m-%d %H:%M")
			end_date_for_cron = tmp_datetime.strftime("%M %H %d %m") + " *"
			print("End date for crontab: " + end_date_for_cron)

			# Use the contab to plan a record between the two dates.
			output_file_path = os.environ['HOME'] + os.sep + self.channel_name.replace(" ","") + "__" + start_date.replace(" ", "__").replace(":", "-") + ".ts"
			output_file_path = output_file_path.encode('utf-8')
			print(output_file_path)

			# Write a temporary file with the crontab tasks.
			try:
				tasks_file = open(g_tasks_file,'w')
				tasks_file.write(start_date_for_cron + " " + g_vlc + " \"" + self.channel_url + "\" --intf dummy --sout file/ts:" + output_file_path + "\n")
				tasks_file.write(end_date_for_cron + " killall vlc && crontab -r\n")
				tasks_file.close()
			except IOError as msg:
				print("ERROR: Cannot write in " + g_tasks_file)
				print(msg)
				sys.exit(1)

			# Load the temporary file in crontab.
			command = "crontab " + g_tasks_file
			os.system(command)












##########################################################
# Main
##########################################################

if __name__ == "__main__":

	# Display the main window
        Ui_MainWindow()
	Gtk.main()



